package com.youshang.aspect;

import com.alibaba.fastjson.JSONObject;
import com.google.common.base.Throwables;
import com.google.common.collect.Lists;
import com.youshang.annotation.AccessLimit;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 定义幂等Aspect
 */
@Component
@Aspect
public class IdempotentAspect {
    Logger log = LoggerFactory.getLogger(IdempotentAspect.class);

    @Autowired
    private RedisTemplate redisTemplate;
    private volatile Integer number;


    @Around("@annotation(com.youshang.annotation.AccessLimit)")
    public Object preHandle(ProceedingJoinPoint joinPoint) throws Throwable {
        try {
            RequestAttributes ra = RequestContextHolder.getRequestAttributes();
            ServletRequestAttributes sra = (ServletRequestAttributes) ra;
            HttpServletRequest request = sra.getRequest();
            String url = request.getRequestURL().toString();

            //获取当前注解信息
            MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
            Method method = methodSignature.getMethod();
            AccessLimit accessLimit = method.getAnnotation(AccessLimit.class);

            //获取注解自定义的参数信息
            int seconds = accessLimit.seconds();
            int concurrencyCount = accessLimit.retryCount();
            boolean login = accessLimit.needLogin();
            if (seconds <= 0) {
                throw new RuntimeException("加载时间不允许小于0");
            }

            //获取请求参数 并制作hashCode
            String queryString = "";
            if (null != request.getQueryString()) {
                queryString = request.getQueryString();
            }
            Object[] args = joinPoint.getArgs();
            String jsonStr = "";
            if (args != null && args.length > 0) {
                jsonStr = JSONObject.toJSONString(args);
            }
            int requestHashCode = jsonStr.concat(queryString).hashCode();

            //获取用户ID
            String userId = "";
            if (login) {
                userId = "0001";
            }

            //key = 请求路径 + 操作用户ID + 请求参数的转String的hashCode值
            //key(String) 调用 intern 之后当前锁才会生效
            String key = (url + userId + requestHashCode).intern();
            log.info("key：{}",key.hashCode());
            synchronized (key) {
                Object value = redisTemplate.opsForValue().get(key);
                log.info("校验接口幂等：当前用户id信息:{},当前接口信息:{},当前key：{},当前redis-value:{}", userId, url, key, value);
                if (null == value) {
                    //当用户第一次访问时，存储当前用户信息+请求路径
                    redisTemplate.opsForValue().set(key, 0, seconds, TimeUnit.SECONDS);
                } else {
                    int accessCount = (int) value;
                    if (concurrencyCount > accessCount) {
                        log.info("当前幂等接口允许并发操作:key:{},最大并发量：{},目前并发量:{}", key, concurrencyCount, accessCount);
                        redisTemplate.opsForValue().increment(key, 1);
                    } else {
                        Long expire = redisTemplate.opsForValue().getOperations().getExpire(key);
                        throw new RuntimeException("当前操作正在进行中，请稍等 " + expire + " 秒后再试");
                    }
                }
            }
        }catch (RuntimeException e){
            throw new RuntimeException(e.getMessage());
        }catch (Exception e){
            log.error("接口幂等拦截失败,异常信息:{}", Throwables.getStackTraceAsString(e));
        }
        return joinPoint.proceed(joinPoint.getArgs());
    }

    public static void main(String[] args) {
        String str1 = "123";
        String str2 = new String("123");
        String str3 = str2.intern();

        System.out.println(str1 == str2);
        System.out.println("----------------------");

        System.out.println(str1 == str3);
        System.out.println("----------------------");

        System.out.println(str2 == str3);


//        AtomicInteger num = new AtomicInteger();
//        ArrayList<Integer> integers = Lists.newArrayList(1, 2, 3, 4, 5);
//        integers.forEach(item -> {
//            num.addAndGet(item);
//        });
//        System.out.println(num);

    }
}
